// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 垃圾收集器（GC）。
// 
// GC与mutator线程并行运行，类型精确（又称精确），允许多个
// GC线程并行运行。它是一种使用写屏障的并发标记和扫描。这是
// 不分代、不紧凑。分配是使用按P分配
// /区域隔离的大小来完成的，以最小化碎片，同时在常见情况下消除锁。
// 
// 算法分解为几个步骤。
// 这是对所用算法的高级描述。对于GC的概述，一个很好的
// 可以从Richard Jones的gchandbook开始。组织。
// 
// 该算法的知识遗产包括Dijkstra的动态算法，请参见
// Edsger W.Dijkstra、Leslie Lamport、A.J.Martin、C.s.Scholten和E.F.M.Steffens。1978.
// 动态垃圾收集：合作练习。公社。ACM 21,11（1978年11月），
// 966-975。
// 有关这些步骤完整、正确和终止的期刊质量证明，请参见
// 哈德逊，R.和莫斯，J.E.B.复制垃圾收集而不停止世界。
// 并发与计算：实践与经验15（3-5），2003年。
// 
// 1。GC执行扫描终止。
// 
// a.阻止世界。这会使所有Ps达到GC安全点。
// 
// b.扫描所有未拉伸的跨度。只有在
// 此GC循环在预期时间之前被强制时，才会有未拉伸的跨度。
// 
// 2。GC执行标记阶段。
// 
// a.通过将gcphase设置为gcmarkabcdefg
// 辅助，并将根标记作业排队，为标记阶段做好准备。在所有Ps启用写屏障（即
// 使用STW完成）之前，不得扫描任何对象。
// 
// b.开创世界。从这一点来看，GC工作由mark 
// 调度程序启动的工作人员和作为分配的一部分执行的辅助人员完成。写入障碍会对
// 覆盖的指针和任何指针
// 写入的新指针值进行着色（有关详细信息，请参阅mbarrier.go）。新分配的对象
// 立即标记为黑色。
// 
// c.GC执行根标记作业。这包括扫描所有
// 堆栈，着色所有全局，以及着色
// 堆外运行时数据结构中的所有堆指针。扫描堆栈会停止
// goroutine，对堆栈上找到的任何指针进行着色，然后
// 恢复goroutine。
// 
// d.GC将灰色对象的工作队列中的所有指针清空，将每个灰色
// 对象扫描为黑色，并将对象
// （这反过来可能会将这些指针添加到工作队列中）中。
// 
// e.由于GC工作分布在本地缓存中，GC使用
// 分布式终止算法来检测何时没有
// 更多根标记作业或灰色对象（请参见gcMarkDone）。此时，GC转换为标记终止。
// 
// 3。GC执行标记终止。
// 
// a.阻止世界。
// 
// b.将gcphase设置为GCmarktermination，并禁用工人和
// 协助。
// 
// c.像冲洗麦卡奇一样执行内务管理。
// 
// 4。GC执行扫描阶段。
// 
// a.通过将gcphase设置为_GCoff，
// 设置扫描状态并禁用写屏障，为扫描阶段做好准备。
// 
// b.开创世界。从现在开始，新分配的对象
// 为白色，如有必要，在使用前分配扫掠范围。
// 
// c.GC在后台执行并发扫描，并响应
// 。见下面的描述。
// 
// 5。当分配足够时，重播序列
// 从上面的1开始。见下文关于GC速率的讨论。

// 并行扫描。
// 
// 扫描阶段与正常程序执行同时进行。
// 堆被惰性地（当一个goroutine需要另一个span时）逐span扫描
// 同时在后台goroutine中扫描（这有助于不受CPU限制的程序）。
// 在STW标记终止的末尾，所有跨度都标记为“需要清扫”。
// 
// 背景清理程序goroutine只需逐个扫描跨区。
// 
// 为了避免在存在未扩展的跨距时请求更多操作系统内存，当
// goroutine需要另一个跨距时，它首先尝试通过扫描来回收这么多内存。当一个goroutine需要分配一个新的小对象范围时，它会清除相同对象大小的小对象范围，直到释放至少一个对象。当goroutine需要从堆中分配大对象跨度时，
// 它会扫描跨度，直到它将至少那么多页释放到堆中。有一种情况可能不够：如果一个goroutine扫描并释放两个不相邻的一页跨距到堆中，它将分配一个新的两页跨距，但仍然可以有其他一页未扫描跨距，它们可以合并成两页跨距。
// 
// 确保在未扫描的跨距上不进行任何操作（这将损坏
// GC位图中的标记位）是至关重要的。在GC过程中，所有的McCache都会被刷新到中央缓存
// 中，因此它们是空的。当一个goroutine在mcache中抓住一个新的跨度时，它会扫过它。
// 当一个goroutine显式释放一个对象或设置一个终结器时，它确保扫描范围（通过扫描它或等待并发扫描完成）。
// 只有在扫描所有跨距时，终结器goroutine才会启动。
// 当下一个GC启动时，它将扫描所有尚未扫描的跨度（如果有）。

// GC速率。
// 下一次GC是在我们分配了与
// 已经使用的内存量成比例的额外内存量之后。该比例由GOGC环境变量
// /（默认为100）控制。如果GOGC=100，我们使用的是4M，那么当我们达到8M 
// （这个标记在gcController.heapGoal变量中跟踪）时，我们将再次进行GC。这使得
// 中的GC成本与分配成本成线性比例。调整GOGC只会改变线性常数
// /（以及额外使用的内存量）。

// Oblets 
// 
// 为了防止扫描大型对象时出现长时间暂停，并提高并行性，垃圾收集器将大于maxObletBytes的
// 对象的扫描作业分解为最多为
// maxObletBytes的“Oblets”。当扫描遇到一个大的
// 对象的开头时，它只扫描第一个小对象，并将剩余的
// 小对象列为新的扫描作业。

package runtime

import (
	"internal/cpu"
	"runtime/internal/atomic"
	"unsafe"
)

const (
	_DebugGC         = 0
	_ConcurrentSweep = true
	_FinBlockSize    = 4 * 1024

	// debugScanConservative为堆栈
	// 保守扫描的帧启用调试日志记录。
	debugScanConservative = false

	// sweepMinHeapDistance是堆距离
	// （字节）的下限，保留用于GC 
	// 周期之间的并发扫描。
	sweepMinHeapDistance = 1024 * 1024
)

func gcinit() {
	if unsafe.Sizeof(workbuf{}) != _WorkbufSize {
		throw("size of Workbuf is suboptimal")
	}
	// 第一个周期没有扫描。
	sweep.active.state.Store(sweepDrainedMask)

	// 初始化GC起搏器状态。
	// 使用环境变量GOGC作为初始gcPercent值。
	gcController.init(readGOGC())

	work.startSema = 1
	work.markDoneSema = 1
	lockInit(&work.sweepWaiters.lock, lockRankSweepWaiters)
	lockInit(&work.assistQueue.lock, lockRankAssistQueue)
	lockInit(&work.wbufSpans.lock, lockRankWbufSpans)
}

// gcenable在运行时初始化的大部分之后调用，
// 就在我们即将开始让用户代码运行之前。
// 它启动后台清理程序goroutine，后台
// 清除程序goroutine，并启用GC。
func gcenable() {
	// 开始清扫和清理。
	c := make(chan int, 2)
	go bgsweep(c)
	go bgscavenge(c)
	<-c
	<-c
	memstats.enablegc = true // 现在运行时已经初始化，GC已经正常了
}

// 垃圾收集器阶段。
// 表示要执行的写入屏障和同步任务。
var gcphase uint32

// 编译器知道这个变量。
// 如果要更改它，必须更改builtin/runtime。也去吧。
// 如果更改前四个字节，还必须更改写入
// 屏障插入代码。
var writeBarrier struct {
	enabled bool    // 编译器在调用写屏障
	pad     [3]byte // 编译器对“enabled”字段
	needed  bool    // 当前GC阶段是否需要写屏障
	cgo     bool    // cgo检查
	alignme uint64  // 确保对齐，以便编译器可以使用32或64位加载
}

// gcBlackenEnabled为1，如果mutator协助，背景标记
// 允许工人对物体进行涂黑。只有当
// gcphase==\U GCmark时，才能设置此选项。
var gcBlackenEnabled uint32

const (
	_GCoff             = iota // GC未运行；后台扫描，禁用写屏障
	_GCmark                   // GC标记根和工作区：分配黑色，启用写屏障
	_GCmarktermination        // GC标记终止：分配黑色，P的帮助GC，启用写屏障
)

// go:nosplit 
func setGCPhase(x uint32) {
	atomic.Store(&gcphase, x)
	writeBarrier.needed = gcphase == _GCmark || gcphase == _GCmarktermination
	writeBarrier.enabled = writeBarrier.needed || writeBarrier.cgo
}

// gcMarkWorkerMode表示并发标记工作区
// 应在的模式下操作。
// 
// 并发标记通过四种不同的机制进行。一个
// 是mutator assists，它是响应分配而发生的，是
// 未计划的。另外三个是per-P标记
// workers的变体，并通过gcMarkWorkerMode进行区分。
type gcMarkWorkerMode int

const (
	// gcMarkWorkerNotWorker表示下一个计划的G不是
	// 正在开始工作，应忽略该模式。
	gcMarkWorkerNotWorker gcMarkWorkerMode = iota

	// gcMarkWorkerDedicatedMode表示一个标记
	// worker的P专用于运行该标记worker。标记
	// 工人应在没有优先权的情况下运行。
	gcMarkWorkerDedicatedMode

	// gcMarkWorkerFractionalMode表示P当前正在运行“分数”标记工作程序。当GOMAXPROCS*gcBackgroundUtilization不是一个整数时，需要使用小数工作者
	// 并且只使用专用工作者将导致
	// 利用率与gcBackgroundUtilization的目标相差太远。
	// fractal worker应该一直运行，直到它被抢占，并且
	// 将被安排提取
	// GOMAXPROCS*gcBackgroundUtilization的小数部分。
	gcMarkWorkerFractionalMode

	// gcMarkWorkerIdleMode表示一个P正在运行标记
	// worker，因为它没有其他事情要做。空闲工作进程
	// 应该运行到被抢占为止，并根据gcController计算其时间
	// 。空闲时间。
	gcMarkWorkerIdleMode
)

// gcMarkWorkerModeStrings是gcMarkWorkerModes 
// 的字符串标签，用于执行跟踪。
var gcMarkWorkerModeStrings = [...]string{
	"Not worker",
	"GC (dedicated)",
	"GC (fractional)",
	"GC (idle)",
}

// PollFractalWorkerXit报告分数标记工作程序
// 是否应自行抢占。它假定它是从分数
// worker调用的。
func pollFractionalWorkerExit() bool {
	// 这应该与findRunnablegWorker中的分数工作程序
	// 调度程序逻辑保持同步。
	now := nanotime()
	delta := now - gcController.markStartTime
	if delta <= 0 {
		return true
	}
	p := getg().m.p.ptr()
	selfTime := p.gcFractionalMarkTime + (now - p.gcMarkWorkerStartTime)
	// 为利用率目标添加一些空闲时间，以使部分工作线程在退出时不会再次落后。
	return float64(selfTime)/float64(delta) > 1.2*gcController.fractionalUtilizationGoal
}

var work struct {
	full  lfstack          // 完整块的无锁列表workbuf 
	empty lfstack          // 空块的无锁列表workbuf 
	pad0  cpu.CacheLinePad // 防止完整/空和nproc/nwait 

	wbufSpans struct {
		lock mutex
		// free是一个专门用于workbuf的跨距列表，但
		// 当前不包含任何workbuf。
		free mSpanList
		// busy是一个包含所有跨度的列表，其中包含一个workbuf列表上的workbuf。
		busy mSpanList
	}

	// 在32位上恢复64位对齐。
	_ uint32

	// bytesMarked是本周期标记的字节数。此
	// 包括扫描对象中变黑的字节、直接变黑的noscan对象
	// 以及在并发扫描阶段由
	// markroot扫描的permagrey对象。这是在循环过程中以原子方式更新的
	// 。由于该值仅在
	// 任意更新。
	// 周期结束时读取，因此更新可能会被分批处理
	// 
	// 由于标记期间的良性竞争，此数字可能不是
	// 标记字节的确切数量，但它应该非常
	// 接近。
	// 
	// 将此字段放在这里，因为它需要64位原子访问
	// （因此即使在32位体系结构上也需要8字节对齐）。
	bytesMarked uint64

	markrootNext uint32 // 下一个markroot作业
	markrootJobs uint32 // markroot作业数

	nproc  uint32
	tstart int64
	nwait  uint32

	// 各种根类型的根数。由gcMarkRootPrepare设置。
	// 
	// nStackRoots==len（stackRoots），但我们有nStackRoots用于
	// 一致性。
	nDataRoots, nBSSRoots, nSpanRoots, nStackRoots int

	// 每个根类型的基本索引。由gcMarkRootPrepare设置。
	baseData, baseBSS, baseSpans, baseStacks, baseEnd uint32

	// stackRoots是在并发标记开始之前存在的所有G的快照。此文件的后台
	// 存储不能修改，因为它可能是
	// 与allgs共享的。
	stackRoots []*g

	// 每种类型的GC状态转换都由锁保护。
	// 由于多个线程可以同时检测到状态
	// 转换条件，任何检测到转换
	// 条件的线程都必须获得相应的转换锁，
	// 重新检查转换条件，如果没有
	// 则返回，如果它继续保持或执行转换，则返回。
	// 同样，在释放锁之前，任何转换都必须使转换
	// 条件无效。这确保了每个
	// 转换都由一个线程执行，并且需要转换发生的线程
	// 会被阻止，直到发生
	// 为止。
	// 
	// startSema保护从“关闭”到标记或
	// 标记终止的转换。
	startSema uint32
	// markDoneSema保护从标记到标记终止的转换。
	markDoneSema uint32

	bgMarkReady note   // 在后台标记完成点
	bgMarkDone  uint32 // cas为1。
	// 后台标记完成信令

	// 模式为当前GC循环的并发模式时，信号后台标记工作程序已启动
	mode gcMode

	// userForced表示当前GC循环是由
	// 显式用户调用强制的。
	userForced bool

	// totaltime是自
	// 程序在调试时启动以来，在GC中花费的CPU纳秒数。gctrace>0。
	totaltime int64

	// initialHeapLive是gcController的值。heapLive在
	// 这个GC循环的开始。
	initialHeapLive uint64

	// assistQueue是由于
	// 既没有足够的信用来窃取，也没有足够的工作来做而被阻止的助攻队列。
	assistQueue struct {
		lock mutex
		q    gQueue
	}

	// sweepWaiters是当我们从标记终止过渡到扫描时要唤醒的阻塞goroutines列表。
	sweepWaiters struct {
		lock mutex
		list gList
	}

	// cycles是完成的GC循环数，其中GC 
	// 循环是扫描终止、标记、标记终止和
	// 扫描。这与memstats不同。numgc，即
	// 在标记终止时递增。
	cycles uint32

	// 此周期的计时/利用率统计数据。
	stwprocs, maxprocs                 int32
	tSweepTerm, tMark, tMarkTerm, tEnd int64 // nanotime（）阶段开始

	pauseNS    int64 // 本周期总STW时间
	pauseStart int64 // 最后一次STW的nanotime（）

	// 调试。gctrace此周期的堆大小。
	heap0, heap1, heap2, heapGoal uint64
}

// GC运行垃圾收集并阻止调用方，直到
// 垃圾收集完成。它还可能会阻止整个
// 程序。ABCFDG 
func GC() {
	// 直到一个完整的循环完成，从开始到结束。因此，我们总是想结束当前的周期，开始一个新的周期。这意味着：
	// 
	// 1。在扫描终止中，标记或标记循环终止
	// N，等待标记终止N完成，并将
	// 转换为扫描N。
	// 
	// 2。在扫描N中，帮助扫描N。
	// 
	// 此时我们可以开始一个完整的循环N+1。
	// 
	// 3。通过启动扫描终止N+1触发循环N+1。
	// 
	// 4。等待标记终止N+1完成。
	// 
	// 5。帮助扫掠N+1，直到扫掠完成。
	// 
	// 所有这些都必须写下来，以应对
	// GC可能会自行前进的事实。例如，当我们阻止
	// 直到标记终止N时，我们可能会在循环N+2中醒来。

	// 等待当前扫描终止、标记和标记
	// 终止完成。
	n := atomic.Load(&work.cycles)
	gcWaitOnMark(n)

	// 我们现在处于扫描N或更高级别。触发GC循环N+1，该循环
	// 将在必要时首先完成扫描N，然后输入扫描
	// 终止N+1。
	gcStart(gcTrigger{kind: gcTriggerCycle, n: n + 1})

	// 等待标记终止N+1完成。
	gcWaitOnMark(n + 1)

	// 返回前完成扫描N+1。我们这样做是为了
	// 完成这个循环，也是因为运行时。GC（）经常被用作测试和基准测试的一部分，以使系统进入相对稳定和孤立的状态。
	for atomic.Load(&work.cycles) == n+1 && sweepone() != ^uintptr(0) {
		sweep.nbgsweep++
		Gosched()
	}

	// 调用者可能会认为堆配置文件反映了返回时刚刚完成的循环（历史上这个
	// 是因为这是一个STW GC），但现在
	// 配置文件仍然反映了标记终止N，而不是N+1。
	// 
	// 从循环N+1中释放的所有扫描完成后，
	// 我们可以继续发布堆配置文件。首先，等待清扫结束。（我们知道扫描队列中没有更多的跨距，但我们可能同时有更多的跨距，所以我们必须等待。）
	for atomic.Load(&work.cycles) == n+1 && !isSweepDone() {
		Gosched()
	}

	// 现在我们真正完成了清理，所以我们可以发布
	// 稳定堆配置文件。只有在我们还没有点击
	// /另一个标记终止时，才能这样做。
	mp := acquirem()
	cycle := atomic.Load(&work.cycles)
	if cycle == n+1 || (gcphase == _GCmark && cycle == n+2) {
		mProf_PostSweep()
	}
	releasem(mp)
}

// gcWaitOnMark块，直到GC完成第N个标记阶段。如果GC的
// 已完成此标记阶段，它将立即返回。
func gcWaitOnMark(n uint32) {
	for {
		// 禁用相变。
		lock(&work.sweepWaiters.lock)
		nMarks := atomic.Load(&work.cycles)
		if gcphase != _GCmark {
			// 我们已经完成了这个周期的标记。我们结束了。
			nMarks++
		}
		if nMarks > n {
			unlock(&work.sweepWaiters.lock)
			return
		}

		// 等待扫描终止，标记，并标记
		// 完成循环N的终止。
		work.sweepWaiters.list.push(getg())
		goparkunlock(&work.sweepWaiters.lock, waitReasonWaitForGCCycle, traceEvGoBlock, 1)
	}
}

// gcMode指示GC循环的并发程度。
type gcMode int

const (
	gcBackgroundMode gcMode = iota // 并发GC和扫描
	gcForceMode                    // 立即停止世界GC，并发扫描
	gcForceBlockMode               // 立即停止世界GC和STW扫描（由用户强制）
)

// gcTrigger是启动GC循环的谓词。具体来说，
// 这是gcu关闭阶段的退出条件。
type gcTrigger struct {
	kind gcTriggerKind
	now  int64  // gcTriggerTime:当前时间
	n    uint32 // gcTriggerCycle:开始
}

type gcTriggerKind int

const (
	// gcTriggerHeap表示当堆大小达到
	// 控制器计算的触发堆大小时，应开始一个循环。
	gcTriggerHeap gcTriggerKind = iota

	// gcTriggerTime表示循环应该在
	// 自上次GC循环以来已超过强制GCPeriod纳秒时开始。
	gcTriggerTime

	// gcTriggerCycle表示如果
	// 我们尚未启动循环编号gcTrigger，则应启动循环。n（相对
	// to work.cycles）。
	gcTriggerCycle
)

// 测试报告触发条件是否满足，这意味着
// 已满足GCoff阶段的退出条件。分配时应测试退出
// 条件。
func (t gcTrigger) test() bool {
	if !memstats.enablegc || panicking != 0 || gcphase != _GCoff {
		return false
	}
	switch t.kind {
	case gcTriggerHeap:
		// 非原子访问gcController。他渴望表现。如果我们要在这个上面触发，这个线程只是原子地编写了gcController。不管怎样，我们还是要活下去，到时候我们会看到我们的
		// 自己写的。工作。循环，但占总括。
		return gcController.heapLive >= gcController.trigger
	case gcTriggerTime:
		if gcController.gcPercent.Load() < 0 {
			return false
		}
		lastgc := int64(atomic.Load64(&memstats.last_gc_nanotime))
		return lastgc != 0 && t.now-lastgc > forcegcperiod
	case gcTriggerCycle:
		return int32(t.n-work.cycles) > 0
	}
	return true
}

// gcStart启动GC。它从_GCoff转换到_GCmark（如果
// debug.gcstoptheworld==0）或执行所有GC（如果
// debug.gcstoptheworld！=0）。
// 
// 在某些情况下，
// 在系统堆栈上调用或持有锁时，可能不执行此转换而返回。
func gcStart(trigger gcTrigger) {
	// 由于这是从malloc调用的，而malloc是在
	// 许多可能持有
	// 锁的库中调用的，所以不要试图在不可抢占或
	// 可能不稳定的情况下启动GC。
	mp := acquirem()
	if gp := getg(); gp == mp.g0 || mp.locks > 1 || mp.preemptoff != "" {
		releasem(mp)
		return
	}
	releasem(mp)
	mp = nil

	// 拾取剩余的未扫描/未扫描跨距同时
	// 
	// 如果我们在后台
	// 模式下被调用，这不应该发生，因为比例扫描应该刚刚完成
	// 扫描所有的跨距，但舍入错误等可能会留下
	// 很少的未扫描跨距。在强制模式下，这是必要的，因为
	// GC可以在扫掠循环的任何点强制。
	// 
	// 我们在这里持续检查过渡条件，以防
	// 此G延迟到下一个GC循环。
	for trigger.test() && sweepone() != ^uintptr(0) {
		sweep.nbgsweep++
	}

	// 执行GC初始化和扫描终止
	// 转换。
	semacquire(&work.startSema)
	// 重新检查转换锁下的转换条件。
	if !trigger.test() {
		semrelease(&work.startSema)
		return
	}

	// 有关统计信息，请检查此GC是否由用户强制执行。
	work.userForced = trigger.kind == gcTriggerCycle

	// 在gcstoptheworld调试模式下，相应地升级该模式。
	// 我们在重新检查转换条件后执行此操作，以便
	// 多个检测堆触发器的Goroutine不会
	// 启动多个STW GC。
	mode := gcBackgroundMode
	if debug.gcstoptheworld == 1 {
		mode = gcForceMode
	} else if debug.gcstoptheworld == 2 {
		mode = gcForceBlockMode
	}

	// 好的，我们开始了！停止其他人
	semacquire(&gcsema)
	semacquire(&worldsema)

	if trace.enabled {
		traceGCStart()
	}

	// 检查所有P是否已完成延迟的mcache刷新。
	for _, p := range allp {
		if fg := atomic.Load(&p.mcache.flushGen); fg != mheap_.sweepgen {
			println("runtime: p", p.id, "flushGen", fg, "!= sweepgen", mheap_.sweepgen)
			throw("p mcache not flushed")
		}
	}

	gcBgMarkStartWorkers()

	systemstack(gcResetMarkState)

	work.stwprocs, work.maxprocs = gomaxprocs, gomaxprocs
	if work.stwprocs > ncpu {
		// 这是用来计算STW阶段的CPU时间，
		// 所以它不能超过ncpu，即使GOMAXPROCS是。
		work.stwprocs = ncpu
	}
	work.heap0 = atomic.Load64(&gcController.heapLive)
	work.pauseNS = 0
	work.mode = mode

	now := nanotime()
	work.tSweepTerm = now
	work.pauseStart = now
	if trace.enabled {
		traceGCSTWStart(1)
	}
	systemstack(stopTheWorldWithSema)
	// 在开始并行扫描之前完成扫描。
	systemstack(func() {
		finishsweep_m()
	})

	// clearpools，在我们开始GC之前。如果我们等待，他们的内存将不会被回收，直到下一个GC循环。
	clearpools()

	work.cycles++

	// 从我们开始
	// 世界的那一刻起，援助和工人就可以开始了。
	gcController.startCycle(now, int(gomaxprocs))
	work.heapGoal = gcController.heapGoal

	// 在STW模式下，禁用用户Gs的调度。这也可能
	// 禁用此goroutine的调度，因此一旦我们再次启动世界，它可能会以
	// 的形式阻止。
	if mode != gcBackgroundMode {
		schedEnableUser(false)
	}

	// 进入并发标记阶段并启用
	// 写屏障。
	// 
	// 因为世界停止了，所有的Ps都将
	// 观察到写屏障是由
	// 我们开始世界并开始
	// 扫描时启用的。
	// 
	// 在启用辅助之前必须启用写屏障
	// 因为在标记任何非叶堆对象之前必须启用写屏障。由于分配被阻止，直到助攻发生，我们希望尽早启用助攻。
	setGCPhase(_GCmark)

	gcBgMarkPrepare() // 必须在辅助启用之前发生。
	gcMarkRootPrepare()

	// 标记所有活动的tinyalloc块。因为我们是
	// 从这些分配，所以它们需要像
	// 其他分配一样是黑色的。另一种方法是将
	// 每个分配上的小块变黑，这样会减慢小分配程序的速度。
	gcMarkTinyAllocs()

	// 此时，所有Ps都启用了写
	// 屏障，从而保持了非白色到
	// 黑色不变量。启用突变子帮助
	// 对快速分配
	// 突变子施加压力。
	atomic.Store(&gcBlackenEnabled, 1)

	// 在STW模式下，我们可以阻止即时系统堆栈
	// 返回，因此确保我们不可抢占。
	mp = acquirem()

	// 并发标记。
	systemstack(func() {
		now = startTheWorldWithSema(trace.enabled)
		work.pauseNS += now - work.pauseStart
		work.tMark = now
		memstats.gcPauseDist.record(now - work.pauseStart)
	})

	// 在STW模式下释放Gosched（）之前的世界语义
	// 因为我们以后需要重新获取它，但在
	// 此goroutine再次可运行之前，我们可以
	// 否则会自动死锁。
	semrelease(&worldsema)
	releasem(mp)

	// 确保我们在STW模式下阻止而不是返回用户代码
	// 。
	if mode != gcBackgroundMode {
		Gosched()
	}

	semrelease(&work.startSema)
}

// gcMarkDoneFlushed统计具有刷新工作的P的数量。
// 
// 理想情况下，这将是gcMarkDone中捕获的本地，但forEachP 
// 逃避了其回调闭包，因此无法捕获任何内容。
// 
// 受markDoneSema保护。
var gcMarkDoneFlushed uint32

// gcMarkDone如果标记了所有的
// 可到达的对象（也就是说，没有灰色的
// 对象，将来不能再出现），则将GC从标记转换到标记终止。否则，它会将
// 所有本地工作刷新到全局队列中，
// 其他工作人员可以在其中发现该工作。
// 
// 当所有本地标记工作已排干且
// 没有剩余工人时，应调用此命令。具体来说，当
// 
// 工作时。nwait==工作。nproc&！gcMarkWorkAvailable（p）
// 
// 调用上下文必须是可抢占的。
// 
// 刷新本地工作非常重要，因为空闲的Ps可能会让本地
// 工作排队。这是让这项工作可见的唯一方法，也是推动GC完成的唯一方法。
// 
// 在这个函数中设置写障碍是可以的。如果
// 它确实转换为标记终止，那么所有可访问的对象
// 都已标记，因此写屏障不能再隐藏任何
// 对象。
func gcMarkDone() {
	// 确保一次只有一个线程运行参差不齐的屏障。
	semacquire(&work.markDoneSema)

top:
	// 重新检查转换锁下的转换条件。
	// 
	// 在执行不规则屏障之前，检查全局工作队列是否为空非常重要。否则，在P通过破败的障碍后，P可能需要进行全球性的工作。
	if !(gcphase == _GCmark && work.nwait == work.nproc && !gcMarkWorkAvailable(nil)) {
		semrelease(&work.markDoneSema)
		return
	}

	// forEachP需要worldsema来执行，我们需要它来
	// 稍后停止世界，所以现在就收购worldsema。
	semacquire(&worldsema)

	// 刷新所有本地缓冲区并收集flushedWork标志。
	gcMarkDoneFlushed = 0
	systemstack(func() {
		gp := getg().m.curg
		// 将用户堆栈标记为可抢占，以便扫描。
		// 否则，我们试图将所有P强制到一个安全点可能会导致死锁，因为我们试图抢占一个属于
		// 试图抢占我们（例如，进行堆栈扫描）。
		casgstatus(gp, _Grunning, _Gwaiting)
		forEachP(func(_p_ *p) {
			// 刷新写屏障缓冲区，因为这可能会将
			// 工作添加到gcWork。
			wbBufFlush1(_p_)

			// 刷新gcWork，因为这可能会创建全局工作
			// 并设置flushedWork标志。
			// 
			// TODO（奥斯汀）：将这些工作分解为
			// 更好地分配工作。
			_p_.gcw.dispose()
			// 收集flushedWork标志。
			if _p_.gcw.flushedWork {
				atomic.Xadd(&gcMarkDoneFlushed, 1)
				_p_.gcw.flushedWork = false
			}
		})
		casgstatus(gp, _Gwaiting, _Grunning)
	})

	if gcMarkDoneFlushed != 0 {
		// 自上次终止检查以来，发现了更多灰色对象，因此可能还有更多的
		// 工作要做。继续前进。
		// 过渡条件可能在
		// 参差不齐的屏障期间再次变为真，因此请重新检查。
		semrelease(&worldsema)
		goto top
	}

	// 自从我们攻读markDoneSema以来，没有全球工作，没有本地工作，也没有Ps 
	// 沟通工作。因此
	// 没有灰色对象，也没有更多对象可以被
	// 着色。过渡到标记终止。
	now := nanotime()
	work.tMarkTerm = now
	work.pauseStart = now
	getg().m.preemptoff = "gcing"
	if trace.enabled {
		traceGCSTWStart(0)
	}
	systemstack(stopTheWorldWithSema)
	// gcphase为_GCmark，将过渡到_GCmark终止
	// 以下。重要的是，wb保持活动状态，直到
	// 所有标记完成。这包括GC进行的写入。

	// 由于
	// 在上述完成屏障之后执行写入屏障，当我们进入标记终止时，有时会有剩余的工作。
	// 检测此并恢复并发标记。这显然是不幸的。
	// 
	// 有关详细信息，请参阅第27993期。
	// 
	// 切换到系统堆栈以调用wbBufFlush1，但在本例中，
	// 这并不重要，因为我们无论如何都是不可抢占的。
	restart := false
	systemstack(func() {
		for _, p := range allp {
			wbBufFlush1(p)
			if !p.gcw.empty() {
				restart = true
				break
			}
		}
	})
	if restart {
		getg().m.preemptoff = ""
		systemstack(func() {
			now := startTheWorldWithSema(true)
			work.pauseNS += now - work.pauseStart
			memstats.gcPauseDist.record(now - work.pauseStart)
		})
		semrelease(&worldsema)
		goto top
	}

	// 禁用助手和后台工作人员。我们必须在解围前完成
	// 这一步。
	atomic.Store(&gcBlackenEnabled, 0)

	// 唤醒所有被阻挡的助攻。当我们再次启动世界时，这些将运行。
	gcWakeAllAssists()

	// 同样，释放转换锁。被封锁的
	// 当我们再次启动
	// 世界时，工人和助手将运行。
	semrelease(&work.markDoneSema)

	// 在STW模式下，重新启用用户goroutines。这些将是
	// 在我们启动世界后排队运行。
	schedEnableUser(true)

	// 结束周期取决于正在刷新的所有gcWork缓存统计信息。
	// 上述终止算法确保了自参差不齐的屏障以来最多
	// 分配。
	nextTriggerRatio := gcController.endCycle(now, int(gomaxprocs), work.userForced)

	// 执行标记终止。这将重启世界。
	gcMarkTermination(nextTriggerRatio)
}

// 世界必须停止，马克助手和背景工人必须被
// 禁用。
func gcMarkTermination(nextTriggerRatio float64) {
	// 开始标记终止（写入屏障暂时保持启用状态）。
	setGCPhase(_GCmarktermination)

	work.heap1 = gcController.heapLive
	startTime := nanotime()

	mp := acquirem()
	mp.preemptoff = "gcing"
	_g_ := getg()
	_g_.m.traceback = 2
	gp := _g_.m.curg
	casgstatus(gp, _Grunning, _Gwaiting)
	gp.waitreason = waitReasonGarbageCollection

	// 在g0堆栈上运行gc。我们这样做是为了使我们当前运行的g堆栈
	// 不再改变。切掉
	// 根设置了一点（不扫描g0堆栈，
	// 我们不需要扫描gc的内部状态）。我们还需要切换到g0以便缩小堆栈。
	systemstack(func() {
		gcMark(startTime)
		// 必须立即返回。
		// 在gcMark期间，外部函数的堆栈可能已经移动了
		// 了（它会收缩堆栈，包括
		// 外部函数的堆栈），所以我们不能引用
		// 它的任何变量。返回
		// 非系统堆栈以获取新地址
		// 然后继续。
	})

	systemstack(func() {
		work.heap2 = work.bytesMarked
		if debug.gccheckmark > 0 {
			// 运行完全非并行，停止世界
			// 使用复选标记位进行标记，以检查我们
			// 在
			// 并发标记过程中没有忘记标记任何东西。
			startCheckmarks()
			gcResetMarkState()
			gcw := &getg().m.p.ptr().gcw
			gcDrain(gcw, 0)
			wbBufFlush1(getg().m.p.ptr())
			gcw.dispose()
			endCheckmarks()
		}

		// 标记已完成，因此我们可以关闭写屏障
		setGCPhase(_GCoff)
		gcSweep(work.mode)
	})

	_g_.m.traceback = 0
	casgstatus(gp, _Gwaiting, _Grunning)

	if trace.enabled {
		traceGCDone()
	}

	// 全部完成
	mp.preemptoff = ""

	if gcphase != _GCoff {
		throw("gc done but gcphase != _GCoff")
	}

	// 记录清除程序的堆使用情况。
	memstats.last_heap_inuse = memstats.heap_inuse

	// 为下一个周期更新GC触发器和起搏。
	gcController.commit(nextTriggerRatio)
	gcPaceSweeper(gcController.trigger)
	gcPaceScavenger(gcController.heapGoal, gcController.lastHeapGoal)

	// 更新时间memstats 
	now := nanotime()
	sec, nsec, _ := time_now()
	unixNow := sec*1e9 + int64(nsec)
	work.pauseNS += now - work.pauseStart
	work.tEnd = now
	memstats.gcPauseDist.record(now - work.pauseStart)
	atomic.Store64(&memstats.last_gc_unix, uint64(unixNow)) // 必须是Unix时间才能让用户理解
	atomic.Store64(&memstats.last_gc_nanotime, uint64(now)) // 我们的单调时间
	memstats.pause_ns[memstats.numgc%uint32(len(memstats.pause_ns))] = uint64(work.pauseNS)
	memstats.pause_end[memstats.numgc%uint32(len(memstats.pause_end))] = uint64(unixNow)
	memstats.pause_total_ns += uint64(work.pauseNS)

	// 更新工作。总时间。
	sweepTermCpu := int64(work.stwprocs) * (work.tMark - work.tSweepTerm)
	// 我们在下面报告空闲标记时间，但从
	// 整体利用率中省略它，因为它是“免费的”。
	markCpu := gcController.assistTime + gcController.dedicatedMarkTime + gcController.fractionalMarkTime
	markTermCpu := int64(work.stwprocs) * (work.tEnd - work.tMarkTerm)
	cycleCpu := sweepTermCpu + markCpu + markTermCpu
	work.totaltime += cycleCpu

	// 计算总的GC CPU利用率。
	totalCpu := sched.totaltime + (now-sched.procresizetime)*int64(gomaxprocs)
	memstats.gc_cpu_fraction = float64(work.totaltime) / float64(totalCpu)

	// 重置扫描状态。
	sweep.nbgsweep = 0
	sweep.npausesweep = 0

	if work.userForced {
		memstats.numforcedgc++
	}

	// Bump GC循环计数和wake goroutines等待扫描。
	lock(&work.sweepWaiters.lock)
	memstats.numgc++
	injectglist(&work.sweepWaiters.list)
	unlock(&work.sweepWaiters.lock)

	// 完成当前堆分析周期，并开始新的
	// 堆分析周期。我们在开始世界杯之前就这么做了，这样事件就不会进入错误的循环。
	mProf_NextCycle()

	// 麦卡奇可能有陈旧的跨度需要清扫。
	// 在任何扫描列表中都没有跟踪到这些，所以我们需要根据扫描完成情况对它们进行计数，直到我们确保所有的
	// 这些跨度都已被挤出。
	sl := sweep.active.begin()
	if !sl.valid {
		throw("failed to set sweep barrier")
	}

	systemstack(func() { startTheWorldWithSema(true) })

	// 刷新堆配置文件，以便我们可以在下一个GC开始新的循环。
	// 这是相对昂贵的，所以我们不使用
	// 世界停止了。
	mProf_Flush()

	// 准备清扫工释放的工作包。我们以异步方式执行
	// 因为这可能需要大量时间。
	prepareFreeWorkbufs()

	// 自由堆栈跨距。这必须在GC循环之间完成。
	systemstack(freeStackSpans)

	// 确保所有麦卡奇都冲洗干净。每个P将在分配前刷新自己的
	// mcache，但空闲的P可能不会。由于此
	// 是扫描所有跨度所必需的，因此我们需要确保在开始下一个GC循环之前，所有
	// mcaches都已刷新。
	systemstack(func() {
		forEachP(func(_p_ *p) {
			_p_.mcache.prepareForSweep()
		})
	})
	// 既然我们在麦卡奇扫过了陈旧的跨度，他们就不把未扫过的跨度计算在内了。
	sweep.active.end(sl)

	// 在删除worldsema之前打印gctrace。一旦我们放弃
	// worldsema，另一个周期可能会开始并打破统计数据
	// /我们正在尝试打印。
	if debug.gctrace > 0 {
		util := int(memstats.gc_cpu_fraction * 100)

		var sbuf [24]byte
		printlock()
		print("gc ", memstats.numgc,
			" @", string(itoaDiv(sbuf[:], uint64(work.tSweepTerm-runtimeInitTime)/1e6, 3)), "s ",
			util, "%: ")
		prev := work.tSweepTerm
		for i, ns := range []int64{work.tMark, work.tMarkTerm, work.tEnd} {
			if i != 0 {
				print("+")
			}
			print(string(fmtNSAsMS(sbuf[:], uint64(ns-prev))))
			prev = ns
		}
		print(" ms clock, ")
		for i, ns := range []int64{sweepTermCpu, gcController.assistTime, gcController.dedicatedMarkTime + gcController.fractionalMarkTime, gcController.idleMarkTime, markTermCpu} {
			if i == 2 || i == 3 {
				// 用/分隔标记时间组件。小心：另一个GC循环可能现在开始。
				print("/")
			} else if i != 0 {
				print("+")
			}
			print(string(fmtNSAsMS(sbuf[:], uint64(ns))))
		}
		print(" ms cpu, ",
			work.heap0>>20, "->", work.heap1>>20, "->", work.heap2>>20, " MB, ",
			work.heapGoal>>20, " MB goal, ",
			gcController.stackScan>>20, " MB stacks, ",
			gcController.globalsScan>>20, " MB globals, ",
			work.maxprocs, " P")
		if work.userForced {
			print(" (forced)")
		}
		print("\n")
		printunlock()
	}

	semrelease(&worldsema)
	semrelease(&gcsema)

	releasem(mp)
	mp = nil

	// 现在gc已经完成，如果需要，启动终结器线程
	if !concurrentSweep {
		// 给排队的终结器（如果有）一个运行
		Gosched()
	}
}

// gcBgMarkStartWorkers准备后台标记工作程序goroutines的机会。这些
// goroutines在标记阶段之前不会运行，但它们必须在
// 正常G堆栈中工作未停止时启动。呼叫方必须持有
// worldsema。
func gcBgMarkStartWorkers() {
	// 背景标记由per-P G执行。确保每个P都有
	// 背景GC G。
	// 
	// 如果减少gomaxprocs，则不会退出工作Gs。如果再次提出
	// 我们可以重用老工人；不需要创造新员工。
	for gcBgMarkWorkerCount < gomaxprocs {
		go gcBgMarkWorker()

		notetsleepg(&work.bgMarkReady, -1)
		noteclear(&work.bgMarkReady)
		// 现在保证在
		// 其P的下一个findRunnableGCWorker之前将该工作者添加到池中。

		gcBgMarkWorkerCount++
	}
}

// gcBgMarkPrepare为背景标记设置状态。
// 还不能启用突变辅助。
func gcBgMarkPrepare() {
	// 当工作队列为空时，背景标记将停止
	// 并且没有其他工作人员（请注意，由于这是
	// 并发的，这可能是一个瞬态，但标记
	// 终止将清除它）。在背景工人和助手之间，我们不知道会有多少工人，所以我们假装有任意数量的工人，几乎所有的工人都在“等待”。当一名
	// 工人在工作时，它会减少nwait。如果nproc==nwait，
	// 则没有工人。
	work.nproc = ^uint32(0)
	work.nwait = ^uint32(0)
}

// gcBgMarkWorker是gcBgMarkWorker工具中的一个条目。它指向一个
// gcBgMarkWorker goroutine。
type gcBgMarkWorkerNode struct {
	// 未使用的工作人员在无锁堆栈中进行管理。这个领域必须是第一位的。
	node lfnode

	// 这个工人的名字。
	gp guintptr

	// 在公园释放这个m。这用于与unlock 
	// 函数通信，该函数无法访问G的堆栈。在
	// gcBgMarkWorker（）之外未使用。
	m muintptr
}

func gcBgMarkWorker() {
	gp := getg()

	// 我们将节点传递给gopark解锁函数，因此它不能位于堆栈上（请参见gopark）。防止死锁递归
	// 通过禁用抢占来启动GC。
	gp.m.preemptoff = "GC worker init"
	node := new(gcBgMarkWorkerNode)
	gp.m.preemptoff = ""

	node.gp.set(gp)

	node.m.set(acquirem())
	notewakeup(&work.bgMarkReady)
	// 在这一点之后，背景标记工作程序通常由gcController协同调度
	// 。Findrunnablegworker。在P上执行
	// 工作时，由于我们正在处理
	// P-本地工作缓冲区，因此禁用了抢占。当设置抢占标志时，这会使
	// 进入等待gcController唤醒的状态。在适当的时间找到工作人员
	// 。
	// 
	// 启用抢占时（例如，在gcMarkDone中），此工作进程
	// 可能会被抢占，并从runq作为_GrunnableG进行调度。这就是
	// 好；它最终将通过
	// FindRunnablegWorker再次进入公园进行进一步调度。
	// 
	// 由于我们在通知bgMarkReady之前禁用了抢占，我们
	// 保证此G将在下一个
	// FindRunnablegWorker的工作池中。这并不是绝对必要的，但它可以降低
	// 标记启动和工作人员启动之间的延迟。睡觉直到被叫醒。Findrunnablegworker。

	for {
		gopark(func(g *g, nodep unsafe.Pointer) bool {
			node := (*gcBgMarkWorkerNode)(nodep)

			if mp := node.m.ptr(); mp != nil {
				// 工人G不再运行；发布
				// M.
				// 
				// 注意：安全的做法是尽快发布M 
				// 因为我们不再执行P-local mark 
				// 工作。
				// 
				// 然而，由于我们合作停止工作
				// 当gp时。preempt已设置，如果我们在
				// 循环中释放M，那么下面对gopark 
				// 的调用将立即抢占G。这是
				// 也是安全的，但效率低下：G必须
				// 再次调度，只允许再次进入gopark和park 
				// 循环。因此，我们将释放推迟到
				// 停止G后。
				releasem(mp)
			}

			// 将此G释放到池中。
			gcBgMarkWorkerPool.push(&node.node)
			// 注意，此时，G可能会立即被重新调度，并且可能正在运行。
			return true
		}, unsafe.Pointer(node), waitReasonGCWorkerIdle, traceEvGoBlock, 0)

		// 这里不能发生抢占，否则另一个G可能会看到
		// p.gcMarkWorkerMode。

		// 禁用抢占，以便我们可以使用gcw。如果
		// 调度程序想要抢占我们，我们将停止排水，
		// 处理gcw，然后抢占。
		node.m.set(acquirem())
		pp := gp.m.p.ptr() // P不能在禁用抢占的情况下更改。

		if gcBlackenEnabled == 0 {
			println("worker mode", pp.gcMarkWorkerMode)
			throw("gcBgMarkWorker: blackening not enabled")
		}

		if pp.gcMarkWorkerMode == gcMarkWorkerNotWorker {
			throw("gcBgMarkWorker: mode not set")
		}

		startTime := nanotime()
		pp.gcMarkWorkerStartTime = startTime

		decnwait := atomic.Xadd(&work.nwait, -1)
		if decnwait == work.nproc {
			println("runtime: work.nwait=", decnwait, "work.nproc=", work.nproc)
			throw("work.nwait was > work.nproc")
		}

		systemstack(func() {
			// 将我们的goroutine标记为可抢占，这样就可以扫描它的堆栈
			// 了。这允许两个mark Worker 
			// 相互扫描（否则，他们将
			// 死锁）。我们不能修改
			// G堆栈上的任何内容。但是，堆栈收缩是
			// 为标记工作程序禁用的，因此从G堆栈读取
			// 是安全的。我们被先发制人了。这是
			casgstatus(gp, _Grunning, _Gwaiting)
			switch pp.gcMarkWorkerMode {
			default:
				throw("gcBgMarkWorker: unexpected gcMarkWorkerMode")
			case gcMarkWorkerDedicatedMode:
				gcDrain(&pp.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit)
				if gp.preempt {
					// 这是一个有用的信号，可以将
					// 全部退出运行
					// 队列，这样它就可以在其他地方运行
					// 了。
					if drainQ, n := runqdrain(pp); n > 0 {
						lock(&sched.lock)
						globrunqputbatch(&drainQ, int32(n))
						unlock(&sched.lock)
					}
				}
				// 回到排水系统，这次
				// 不先发制人。
				gcDrain(&pp.gcw, gcDrainFlushBgCredit)
			case gcMarkWorkerFractionalMode:
				gcDrain(&pp.gcw, gcDrainFractional|gcDrainUntilPreempt|gcDrainFlushBgCredit)
			case gcMarkWorkerIdleMode:
				gcDrain(&pp.gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit)
			}
			casgstatus(gp, _Gwaiting, _Grunning)
		})

		// 说明时间。这是最后一个工人吗？我们的工作用完了吗？
		duration := nanotime() - startTime
		gcController.logWorkTime(pp.gcMarkWorkerMode, duration)
		if pp.gcMarkWorkerMode == gcMarkWorkerFractionalMode {
			atomic.Xaddint64(&pp.gcFractionalMarkTime, duration)
		}

		incnwait := atomic.Xadd(&work.nwait, +1)
		if incnwait > work.nproc {
			println("runtime: p.gcMarkWorkerMode=", pp.gcMarkWorkerMode,
				"work.nwait=", incnwait, "work.nproc=", work.nproc)
			throw("work.nwait > work.nproc")
		}

		// 我们将在这一点之后释放，因此这个P可能运行
		// 其他一些。我们必须清除工作模式以避免
		// 将该模式归因于
		// traceGoStart中的其他（非工作）G。
		pp.gcMarkWorkerMode = gcMarkWorkerNotWorker

		// 如果此工作人员达到背景标记completion 
		// 点，则向主GC goroutine发送信号。
		if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
			// 我们这里不需要P-local缓冲区，允许
			// 抢占，因为我们可能会像常规的
			// 在gcMarkDone中进行调度（锁定阻塞等）。
			releasem(node.m.ptr())
			node.m.set(nil)

			gcMarkDone()
		}
	}
}

// gcMarkWorkAvailable报告在p上执行标记工作程序
// 是否可能有用。p可能为零，在这种情况下，它只检查全球工作来源。
func gcMarkWorkAvailable(p *p) bool {
	if p != nil && !p.gcw.empty() {
		return true
	}
	if !work.full.empty() {
		return true // 全局工作可用
	}
	if work.markrootNext < work.markrootJobs {
		return true // 根扫描工作可用
	}
	return false
}

// gcMark运行标记（或者，对于并发GC，标记终止）
// 所有gcWork缓存必须为空。
// STW此时生效。
func gcMark(startTime int64) {
	if debug.allocfreetrace > 0 {
		tracegc()
	}

	if gcphase != _GCmarktermination {
		throw("in gcMark expecting to see gcphase as _GCmarktermination")
	}
	work.tstart = startTime

	// 检查是否没有剩余的标记工作。
	if work.full != 0 || work.markrootNext < work.markrootJobs {
		print("runtime: full=", hex(work.full), " next=", work.markrootNext, " jobs=", work.markrootJobs, " nDataRoots=", work.nDataRoots, " nBSSRoots=", work.nBSSRoots, " nSpanRoots=", work.nSpanRoots, " nStackRoots=", work.nStackRoots, "\n")
		panic("non-empty mark queue after concurrent mark")
	}

	if debug.gccheckmark > 0 {
		// 当有大量
		// Gs时，这是非常昂贵的，所以只有在同时启用复选标记的情况下才能执行。
		gcMarkRootCheck()
	}
	if work.full != 0 {
		throw("work.full != 0")
	}

	// 删除所有快照。allgs可能已经增长了，在这种情况下
	// 这是对旧备份存储的唯一引用，
	// 没有必要保留它。
	work.stackRoots = nil

	// 清除缓冲区并仔细检查所有gcWork缓存
	// 是否为空。在我们
	// 输入标记终止之前，gcMarkDone应确保这一点。
	// 
	// TODO:如果这个
	// 对STW时间有不可忽略的影响，我们可以在标记之前清除缓冲区。
	for _, p := range allp {
		// 自
		// gcMarkDone屏障以来，写入屏障可能有缓冲指针。然而，由于屏障
		// 确保了所有可到达的对象都被标记，因此所有
		// 必须是指向黑色对象的指针。因此我们
		// 只需丢弃写屏障缓冲区即可。
		if debug.gccheckmark > 0 {
			// 为了进行调试，请刷新缓冲区并使
			// 确保它真的被标记了。
			wbBufFlush1(p)
		} else {
			p.wbBuf.reset()
		}

		gcw := &p.gcw
		if !gcw.empty() {
			printlock()
			print("runtime: P ", p.id, " flushedWork ", gcw.flushedWork)
			if gcw.wbuf1 == nil {
				print(" wbuf1=<nil>")
			} else {
				print(" wbuf1.n=", gcw.wbuf1.nobj)
			}
			if gcw.wbuf2 == nil {
				print(" wbuf2=<nil>")
			} else {
				print(" wbuf2.n=", gcw.wbuf2.nobj)
			}
			print("\n")
			throw("P has cached GC work at end of mark termination")
		}
		// 可能仍有缓存的空缓冲区，我们需要刷新这些缓冲区，因为我们要释放它们。还有
		// 可能存在非零统计数据，因为我们在gcMarkDone屏障后分配了
		// 黑色。
		gcw.dispose()
	}

	// 从每个McCache刷新Scanaloc，因为我们将直接修改
	// heapScan。如果我们稍后再刷新，那么Scanaloc 
	// 可能有不正确的信息。
	// 
	// 请注意，保留这些信息并不重要；我们知道
	// 通过扫描工作，目前heapScan是什么。
	for _, p := range allp {
		c := p.mcache
		if c == nil {
			continue
		}
		c.scanAlloc = 0
	}

	// 重置控制器状态。
	gcController.resetLive(work.bytesMarked)
}

// gcSweep必须在系统堆栈上调用，因为它获取堆
// lock。详见mheap。世界必须被阻止。
// 
// go:systemstack 
func gcSweep(mode gcMode) {
	assertWorldStopped()

	if gcphase != _GCoff {
		throw("gcSweep being done but phase is not GCoff")
	}

	lock(&mheap_.lock)
	mheap_.sweepgen += 2
	sweep.active.reset()
	mheap_.pagesSwept.Store(0)
	mheap_.sweepArenas = mheap_.allArenas
	mheap_.reclaimIndex.Store(0)
	mheap_.reclaimCredit.Store(0)
	unlock(&mheap_.lock)

	sweep.centralIndex.clear()

	if !_ConcurrentSweep || mode == gcForceBlockMode {
		// 特殊情况同步扫描。
		// 记录不必按比例进行清扫。
		lock(&mheap_.lock)
		mheap_.sweepPagesPerByte = 0
		unlock(&mheap_.lock)
		// 急切地清扫所有的跨度。
		for sweepone() != ^uintptr(0) {
			sweep.npausesweep++
		}
		// 渴望自由工作。
		prepareFreeWorkbufs()
		for freeSomeWbufs(false) {
		}
		// 此标记/扫描周期的所有“自由”事件都已发生
		// 因此我们可以立即使此配置文件周期
		// 可用。
		mProf_NextCycle()
		mProf_Flush()
		return
	}

	// 背景扫描。
	lock(&sweep.lock)
	if sweep.parked {
		sweep.parked = false
		ready(sweep.g, 0, true)
	}
	unlock(&sweep.lock)
}

// gcResetMarkState在标记之前重置全局状态（并发
// 或STW），并重置所有G的堆栈扫描状态。
// 
// 这是安全的，没有世界停止，因为在此期间或之后创建的任何Gs都将以重置状态启动。
// 
// gcResetMarkState必须在系统堆栈上调用，因为它获取了
// 堆锁。详见mheap。
// 
// go:systemstack 
func gcResetMarkState() {
	// 这可能会在并发阶段调用，所以请锁定以确保
	// allgs不会更改。
	forEachG(func(gp *g) {
		gp.gcscandone = false // 在gcphasework中设置为true 
		gp.gcAssistBytes = 0
	})

	// 清除页面标记。这是每64GB堆只有1MB，所以这里的
	// 时间非常小。其他软件包的挂钩。运行时\注册表工具清理
	lock(&mheap_.lock)
	arenas := mheap_.allArenas
	unlock(&mheap_.lock)
	for _, ai := range arenas {
		ha := mheap_.arenas[ai.l1()][ai.l2()]
		for i := range ha.pageMarks {
			ha.pageMarks[i] = 0
		}
	}

	work.bytesMarked = 0
	work.initialHeapLive = atomic.Load64(&gcController.heapLive)
}


var poolcleanup func()

func sync_runtime_registerPoolCleanup(f func()) {
	poolcleanup = f
}

func clearpools() {
	// 清除同步。池
	if poolcleanup != nil {
		poolcleanup()
	}

	// 清除中央sudog缓存。
	// 别管per-P缓存，它们的大小有严格的限制。
	// 在将缓存列表放到地板上之前断开缓存列表的连接，
	// 以便对一个条目的悬空引用不会锁定所有条目。
	lock(&sched.sudoglock)
	var sg, sgnext *sudog
	for sg = sched.sudogcache; sg != nil; sg = sgnext {
		sgnext = sg.next
		sg.next = nil
	}
	sched.sudogcache = nil
	unlock(&sched.sudoglock)

	// 清除中央延迟池。
	// 不要管per-P池，它们的大小有严格的限制。
	lock(&sched.deferlock)
	// 在将缓存列表放到地板上之前断开缓存列表的连接，
	// 以便对一个条目的悬空引用不会锁定所有条目。
	var d, dlink *_defer
	for d = sched.deferpool; d != nil; d = dlink {
		dlink = d.link
		d.link = nil
	}
	sched.deferpool = nil
	unlock(&sched.deferlock)
}

// 计时

// itoaDiv将val/（10**dec）格式化为buf。
func itoaDiv(buf []byte, val uint64, dec int) []byte {
	i := len(buf) - 1
	idec := i - dec
	for val >= 10 || i >= idec {
		buf[i] = byte(val%10 + '0')
		i--
		if i == idec {
			buf[i] = '.'
			i--
		}
		val /= 10
	}
	buf[i] = byte(val + '0')
	return buf[i:]
}

// fmtNSAsMS很好地将纳秒格式化为毫秒。
func fmtNSAsMS(buf []byte, ns uint64) []byte {
	if ns >= 10e6 {
		// 格式为毫秒整。
		return itoaDiv(buf, ns/1e6, 0)
	}
	// 格式为两位精度，最多三位小数。
	x := ns / 1e3
	if x == 0 {
		buf[0] = '0'
		return buf[:1]
	}
	dec := 3
	for x >= 100 {
		x /= 10
		dec--
	}
	return itoaDiv(buf, x, dec)
}

// 测试GC的助手。

// gcTestMoveStackOnNextCall使堆栈在调用
// 之后立即移动。
// 如果在此调用之后出现任何其他工作（例如返回），则可能无法正常工作。
// 通常，以下调用应标记为go:noinline，以便执行堆栈检查。
// 
// 在极少数情况下，这可能不会导致堆栈移动，特别是如果
// 在本次调用和下一次调用之间存在抢占。
func gcTestMoveStackOnNextCall() {
	gp := getg()
	gp.stackguard0 = stackForceMove
}

// gcTestIsReachable执行GC并返回一个位集，如果可以访问ptrs[i]，则设置位i 
// 。
func gcTestIsReachable(ptrs ...unsafe.Pointer) (mask uint64) {
	// 这将指针视为不安全的。为了保持
	// 它们的寿命足以让我们附上特价商品。在
	// 之后，我们不再提及它们。

	if len(ptrs) > 64 {
		panic("too many pointers for uint64 mask")
	}

	// 在我们附加特价商品时阻止GC，并将我们的参考
	// 删除到PTR。否则，如果GC正在进行，它可能会在我们有机会删除它们之前，将它们标记为
	// 可以通过此函数访问它们。
	semacquire(&gcsema)

	// 为PTR创建可达性特例。
	specials := make([]*specialReachable, len(ptrs))
	for i, p := range ptrs {
		lock(&mheap_.speciallock)
		s := (*specialReachable)(mheap_.specialReachableAlloc.alloc())
		unlock(&mheap_.speciallock)
		s.special.kind = _KindSpecialReachable
		if !addspecial(p, &s.special) {
			throw("already have a reachable special (duplicate pointer?)")
		}
		specials[i] = s
		// 确保我们不保留PTR。
		ptrs[i] = nil
	}

	semrelease(&gcsema)

	// 强制执行完全GC和扫描。
	GC()

	// 工艺特价。
	for i, s := range specials {
		if !s.done {
			printlock()
			println("runtime: object", i, "was not swept")
			throw("IsReachable failed")
		}
		if s.reachable {
			mask |= 1 << i
		}
		lock(&mheap_.speciallock)
		mheap_.specialReachableAlloc.free(unsafe.Pointer(s))
		unlock(&mheap_.speciallock)
	}

	return mask
}

// gctestpointer类返回p所指向的类别，其中之一是：
// “heap”、“stack”、“data”、“bss”、“other”。这有助于检查
// 测试是否正在执行其预期的操作。
// 
// 这只是为了避免额外的指针洗牌，这可能会使测试复杂化。
// 
// go:nosplit 
func gcTestPointerClass(p unsafe.Pointer) string {
	p2 := uintptr(noescape(p))
	gp := getg()
	if gp.stack.lo <= p2 && p2 < gp.stack.hi {
		return "stack"
	}
	if base, _, _ := findObject(p2, 0, 0); base != 0 {
		return "heap"
	}
	for _, datap := range activeModules() {
		if datap.data <= p2 && p2 < datap.edata || datap.noptrdata <= p2 && p2 < datap.enoptrdata {
			return "data"
		}
		if datap.bss <= p2 && p2 < datap.ebss || datap.noptrbss <= p2 && p2 <= datap.enoptrbss {
			return "bss"
		}
	}
	KeepAlive(p)
	return "other"
}
